package gov.fema.hazus.bdc.client.map;

import gov.fema.hazus.bdc.client.common.BuildingQuery;
import gov.fema.hazus.bdc.client.common.handlers.LoggingAsyncHandler;
import gov.fema.hazus.bdc.client.common.map.MapInterface;
import gov.fema.hazus.bdc.client.common.ui.Message;
import gov.fema.hazus.bdc.client.controller.Controller;
import gov.fema.hazus.bdc.client.services.BuildingServiceProxy;
import gov.fema.hazus.bdc.client.ui.BuildingEditor;
import gov.fema.hazus.bdc.shared.entity.AEBMResults;
import gov.fema.hazus.bdc.shared.entity.Address;
import gov.fema.hazus.bdc.shared.entity.Building;
import gov.fema.hazus.bdc.shared.entity.Location;

import java.util.ArrayList;
import java.util.Iterator;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.maps.client.InfoWindow;
import com.google.gwt.maps.client.InfoWindowContent;
import com.google.gwt.maps.client.MapType;
import com.google.gwt.maps.client.MapWidget;
import com.google.gwt.maps.client.control.LargeMapControl;
import com.google.gwt.maps.client.control.MapTypeControl;
import com.google.gwt.maps.client.event.InfoWindowMaximizeClickHandler;
import com.google.gwt.maps.client.event.InfoWindowRestoreEndHandler;
import com.google.gwt.maps.client.event.MarkerClickHandler;
import com.google.gwt.maps.client.event.MarkerRemoveHandler;
import com.google.gwt.maps.client.event.InfoWindowMaximizeClickHandler.InfoWindowMaximizeClickEvent;
import com.google.gwt.maps.client.event.InfoWindowRestoreEndHandler.InfoWindowRestoreEndEvent;
import com.google.gwt.maps.client.geom.LatLng;
import com.google.gwt.maps.client.overlay.Marker;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

public class Map extends Composite implements MapInterface, ResizeHandler {

	private DockPanel docker = new DockPanel();
	private MapWidget map;
	private HorizontalPanel topnav = new HorizontalPanel();
	private int maxResults = 100000;
	private int nextResult = 1;
	private boolean fetched = false;
	
	private NumberFormat fmt = NumberFormat.getCurrencyFormat();
	
	private BuildingMapHandler mapHandler = new BuildingMapHandler();
	public InfoWindow info;
	
	private static Map singleton;
	public static Map get()
	{
		if( singleton == null )
        {
		    singleton = new Map();
        }
        return singleton;
	}
	
	public Map()
	{
		
		Window.addResizeHandler(this);
		
		// set up MapWidget
		LatLng origin = LatLng.newInstance(40.766664, -111.839769);
		map = new MapWidget(origin, 15);
        map.setSize("100", "100");
        map.addControl(new LargeMapControl());
        map.addControl(new MapTypeControl());
        map.setCurrentMapType(MapType.getHybridMap());
        map.setStyleName("map-widget");
        
        // add it to the dockpanel
        docker.add(map, DockPanel.CENTER);
        
        // add some items to the topnav
        Button refreshButton = new Button("Refresh");
        HTML mapTools = new HTML("Map Tools");
        //topnav.add(refreshButton);
        //topnav.add(mapTools);
        docker.add(topnav, DockPanel.NORTH);
        
        // set the docker style
        docker.setStyleName("docker-map");
        docker.setCellHeight(map, "200px"); // adds blue, but no map
        
		// init
        initWidget(docker);
        
	}
	
	public void resize(int width, int height)
	{
		Map.get().onWindowResized(width, height);
	}
	
	public void onWindowResized(int width, int height) {
		
		GWT.log("--------------------MAP RESIZE---------------------", null);
		
		GWT.log("MAP: resize map and docker", null);
		
		GWT.log("MAP: incoming width=" + width, null);
		GWT.log("MAP: Window.getClientWidth()=" + Window.getClientWidth(), null);
		
		GWT.log("MAP: incoming height=" + height, null);
		GWT.log("MAP: Window.getClientHeight()=" + Window.getClientHeight(), null);
		
		// get the left nav width
		int leftWidth = Controller.get().getLeftNavigationWidth();
		GWT.log("MAP: leftWidth=" + leftWidth, null);
		
		// get the left nav width
		int rightWidth = Controller.get().getRightNavigationWidth();
		GWT.log("MAP: rightWidth=" + rightWidth, null);
		
		// middle width
		int newMapWidth = width - (leftWidth + rightWidth) - 40;
		GWT.log("MAP: newMapWidth=" + newMapWidth, null);
		
		// convert int to string with 'px'
		String mapWidth = Integer.toString(newMapWidth); //w/out 'px'
		if( newMapWidth > 0 )
		{
			GWT.log("MAP: setting map width: " + newMapWidth, null);
			map.setWidth(mapWidth + "px");
		}
		
		// and get the right height
		// css sets top to 20, search to 80 and footer to 40 (.south), another 20 for map border, so subtract 160
        int newMapHeight = height - 160;
        newMapHeight = newMapHeight - 10; // border added via css - see .map in incast.css
        //int northHeight = Controller.get().getMainUINorthHeight();
		//int newMapHeight = height - northHeight - 60; // desired bottom margin
		String mapHeight = Integer.toString(newMapHeight);
        
        if( newMapHeight > 0 )
        {
        	GWT.log("MAP: setting map height: " + newMapHeight, null);
        	map.setHeight(mapHeight + "px");
        }
        
        // after all that, just do it this way...
        map.setWidth("100%");
        
	}

	public void debugSize() {
		GWT.log("map width=" + map.getOffsetWidth(), null);
		GWT.log("map height=" + map.getOffsetHeight(), null);
	}
	
    public void getBuildings()
    {
        BuildingQuery buildingQuery = new BuildingQuery();
        buildingQuery.setMax(maxResults);
        buildingQuery.setAscending(true);
        buildingQuery.setNext(nextResult);
        if( !fetched )
        {
        	BuildingServiceProxy.get().getBuildingLocations(mapHandler);
        }
        
    }
    
    /***************************************************************
     * The actionWrapper methods triggered by the Controller. 
     ***************************************************************/
    
	public static void actionWrapperSetVisible(boolean flag)
	{
		GWT.log("Executing Map.get().actionWrapperSetVisible("+flag+")", null);
		Map.get().setVisible(flag);
	}
	
	public static void actionWrapperMapResize(int width, int height)
	{
		GWT.log("Executing Map.get().actionWrapperMapResize()", null);
		Map.get().resize(width, height);
	}	
	
	public static void actionWrapperGetBuildings()
	{
		GWT.log("Executing Map.get().actionWrapperGetBuildings()", null);
		Map.get().getBuildings();
	}
	
	
    /***************************************************************
     * private classes
     ***************************************************************/
    
    private class BuildingMapHandler extends LoggingAsyncHandler
    {

		@Override
		public void handleFailure(Throwable caught) {
			GWT.log("BuildingMapHandler failure" + caught, null);
	        //TODO Set message with Message.get().show(caught.getMessage());
		}

		@Override
		public void handleSuccess(Object result) {
			
			fetched = true;
			map.clearOverlays();

			try {
			
				/*
				 * The result is an ArrayList of locations
				 * 
				 * Loop through the buildings and fetch the ID for each,
				 * assigning it
				 */
				ArrayList<Location> geolocations = (ArrayList) result;
				GWT.log("BuildingLocations fetched for map=" + geolocations.size(), null);
				
				for (Iterator i = geolocations.iterator(); i.hasNext();) {
	
					final Location geoLocation = (Location) i.next();
					
					// lat/lng from building for marker location
					double lat = geoLocation.getLatitude();
		            double lng = geoLocation.getLongitude();
		            LatLng coordinates = LatLng.newInstance(lat, lng);
		            
		            // the new marker
		            final Marker marker = new Marker(coordinates);
	
		            // when we click on a marker, fetch the whole building
		            marker.addMarkerClickHandler(new MarkerClickHandler(){
						public void onClick(MarkerClickEvent event) {
							Building b = new Building();
							b.setId(geoLocation.getId());
							BuildingServiceProxy.get().getBuilding(b, new MarkerInfoWindowHandler(marker));
						}
		            });
		            
		         
		            // add it to the map
		            GWT.log("Add a new marker to the map at " + lat + "," + lng, null);
		            map.addOverlay(marker);
		            
				}
			} catch (Exception e)
			{
				//TODO: swallow exception? this is really just for null pointers on the
				// building locations result, which could be null during testing
			}
		}
    	
    }
    
    public class BuildingMarkerRemoveHandler implements MarkerRemoveHandler
    {

		public void onRemove(MarkerRemoveEvent event) {
			// TODO Auto-generated method stub
			
		}
    	
    }
    
    public class BuildingMarkerClickHandler implements MarkerClickHandler
    {

        private Long id;

        public BuildingMarkerClickHandler(Long id)
        {
            this.id = id;
        }
        
		public void onClick(MarkerClickEvent event) {
			
			GWT.log("Get info for building ("+this.id+")", null);
			Building building = new Building();
			building.setId(id);
			
			Marker marker = event.getSender();
			BuildingServiceProxy.get().getBuilding(building, new MarkerInfoWindowHandler(marker));
			
		}
    	
    }
    
    private class MarkerInfoWindowHandler extends LoggingAsyncHandler
    {

    	private Marker marker;
    	
    	public MarkerInfoWindowHandler(Marker marker)
    	{
    		this.marker = marker;
    	}
    	
		@Override
		public void handleFailure(Throwable caught) {
			// TODO: throw up an info window window with a problem
			
		}

		@Override
		public void handleSuccess(Object result) {
			
			// grab the building
			final Building building = (Building) result;
			
			// create an info window
			map.closeInfoWindow();
			map.setInfoWindowEnabled(true);
			
			final InfoWindow info = map.getInfoWindow();
			info.setMaximizeEnabled(true);
			
			/*
			info.addInfoWindowCloseClickHandler(new InfoWindowCloseClickHandler(){
				public void onCloseClick(InfoWindowCloseClickEvent event) {
					GWT.log("closing info window", null);
				}
			});
			*/
			
			String buildingLabel = building.getName();
			if( buildingLabel == null )
			{
				building.getAbbreviation();
			}
			HTML h = new HTML(buildingLabel);
			h.setStyleName("map-edit-link-title");
			h.addClickHandler(new ClickHandler() {
				@Override
				public void onClick(ClickEvent event) {
					GWT.log("Edit building from map:" + building, null);
		            BuildingEditor.get().getBuilding(building);
				}
			});
			
			// second line has abbreviation and building id
			String secondLine = "";
			if (building.getAbbreviation() != null)
			{
				secondLine = building.getAbbreviation();
			}
			if (building.getNumber() != null)
			{
				secondLine = "Building #" + building.getNumber();
			}
			
			if( building.getAbbreviation() != null && building.getNumber() != null)
			{
				secondLine = building.getAbbreviation() +", Building #" + building.getNumber();
			}
			HTML secondLineHTML = new HTML(secondLine);
			
			
			// add the AEBM results link
			HTML aebmLink = new HTML("View earthquake analysis");
			aebmLink.setStyleName("map-edit-link");
			aebmLink.addClickHandler(new ClickHandler() {
				@Override
				public void onClick(ClickEvent event) {
					GWT.log("maximize the info window", null);
					info.maximize();
				}
			});
			
			// stick everything in a vpanel
			VerticalPanel vp = new VerticalPanel();
			vp.add(h);
			vp.add(secondLineHTML);
			vp.add(aebmLink);
			
			final InfoWindowContent content = new InfoWindowContent(vp);
			
			final VerticalPanel maxVp = new VerticalPanel();
			content.setMaxContent(maxVp);
			info.addInfoWindowMaximizeClickHandler(new InfoWindowMaximizeClickHandler() {
				@Override
				public void onMaximizeClick(InfoWindowMaximizeClickEvent event) {
					GWT.log("Fetch AEBM data for building:" + building.getId(), null);
					BuildingServiceProxy.get().getBuildingEarthquakeResults(building, new AsyncCallback<Building>() {

						@Override
						public void onFailure(Throwable caught) {
							//TODO: warn user in bubble
						}

						@Override
						public void onSuccess(Building result) {
							
							maxVp.clear();
							
							AEBMResults results = result.getAebmResults();
							
							/* === layout ===
							 * - *Building name (number) - ABBR*
							 * - street1 city state zip
							 * - Total loss: totecoloss
							 * 
							 * TAB 1
							 * - Losses
							 * === str ===           === nonstr ===
							 * loss: $					loss: $
							 * damage state:			damage states:
							 *  (chart)						(chart)
							 * 
							 * TAB 2
							 * - Casualties
							 * === day ===			=== night ===
							 * Level 1				Level 1
							 * Level 2				Level 2
							 * Level 3				Level 3
							 * Level 4				Level 4
							 */
							
							Double totalEconomicLoss = results.getTotalEconomicLoss() * 1000;
							Double totalStructuralLoss = results.getStructuralLoss() * 1000;
							Double totalNonStructuralLoss = results.getNonStructuralLoss() * 1000;
							Double lossContents = results.getContentsLoss() * 1000;
							Double lossBusinessRelated = results.getBusinessRelatedLoss() * 1000;
							
							String totalEconomicLossString = fmt.format(totalEconomicLoss);
							String totalStructuralLossString = fmt.format(totalStructuralLoss);
							String totalNonStructuralLossString = fmt.format(totalNonStructuralLoss);
							String lossContentsString = fmt.format(lossContents);
							String lossBusinessRelatedString = fmt.format(lossBusinessRelated);
							
							
							String maxT = result.getName();
							if( result.getNumber() != null)
							{
								maxT += " ("+result.getNumber()+")";
							}
							if( building.getAbbreviation() != null)
							{
								maxT += " "+result.getAbbreviation();
							}
							
							HTML maxTitle = new HTML("<b>"+maxT+"</b>");
							content.setMaxTitle(maxTitle);
							
							// building info
							Address address = building.getAddress();
							HTML maxHeader = new HTML("<b>"+maxT+"</b>");
							String maxA = address.getStreet1() + " " +
											address.getCity() + " " +
											address.getState() + " " +
											address.getPostalCode();
							HTML maxAddress = new HTML(maxA);
							
							
							maxVp.add(maxHeader);
							maxVp.add(maxAddress);
							
							// economic loss
							HTML totalLoss = new HTML("<b>Total economic loss: "+totalEconomicLossString+"</b>");
							maxVp.add(totalLoss);
							
							/*
							 * http://chart.apis.google.com/chart?cht=p3&chs=250x100
							 * &chd=t:0.02,0.98
							 * &chl=Hello|World
							 */
							
							Image strLossChartImage = new Image();
							try {
								
								String strDsComp = Double.toString(results.getStructuralPercentDamageComplete());
								String strDsExt = Double.toString(results.getStructuralPercentDamageExtensive());
								String strDsMod = Double.toString(results.getStructuralPercentDamageModerate());
								String strDsNone = Double.toString(results.getStructuralPercentDamageNone());
								String strDsSlight = Double.toString(results.getStructuralPercentDamageSlight());
								
								String strLossChartUrl = "http://chart.apis.google.com/chart?cht=p3&chs=300x100";
								strLossChartUrl += "&chd=t:"+strDsComp+","+strDsExt+","+strDsMod+","+strDsSlight+","+strDsNone;
								strLossChartUrl += "&chl=Complete|Extensive|Moderate|Slight|None";
								strLossChartUrl += "&chtt=Structural Damage Potential";
								
								strLossChartImage.setUrl(strLossChartUrl);
								
								
							} catch (Exception e) {
								//TODO: handle number format exception?
								
							}
							
							Image nonStrLossChartImage = new Image();
							try {
								
								String nonStrDsComp = Double.toString(results.getNonStructuralPercentDamageComplete());
								String nonStrDsExt = Double.toString(results.getNonStructuralPercentDamageExtensive());
								String nonStrDsMod = Double.toString(results.getNonStructuralPercentDamageModerate());
								String nonStrDsNone = Double.toString(results.getNonStructuralPercentDamageNone());
								String nonStrDsSlight = Double.toString(results.getNonStructuralPercentDamageSlight());
								
								String nonStrLossChartUrl = "http://chart.apis.google.com/chart?cht=p3&chs=300x100";
								nonStrLossChartUrl += "&chd=t:"+nonStrDsComp+","+nonStrDsExt+","+nonStrDsMod+","+nonStrDsSlight+","+nonStrDsNone;
								nonStrLossChartUrl += "&chl=Complete|Extensive|Moderate|Slight|None";
								nonStrLossChartUrl += "&chtt=Non-structural Damage Potential";
								nonStrLossChartImage.setUrl(nonStrLossChartUrl);
								
							} catch (Exception e) {
								//TODO: handle number format exception?
								
							}									
							
							// losses
							FlexTable maxTable = new FlexTable();
							maxTable.setWidget(0, 0, new HTML("&nbsp;"));
							maxTable.setWidget(0, 1, new HTML("&nbsp;"));
							maxTable.setWidget(1, 0, new HTML("Structural loss: " + totalStructuralLossString));
							maxTable.setWidget(1, 1, new HTML("Non-structural loss: " + totalNonStructuralLossString));
							maxTable.setWidget(2, 0, strLossChartImage);
							maxTable.setWidget(2, 1, nonStrLossChartImage);
							
							maxTable.setWidget(3, 0, new HTML("<b>Day-time casualties</b>"));
							maxTable.setWidget(3, 1, new HTML("<b>Night-time casualties</b>"));
							
							maxTable.setWidget(4, 0, new HTML("Level 1: " + results.getRequiresMedicalAttentionDaytime()));
							maxTable.setWidget(4, 1, new HTML("Level 1: " + results.getRequiresMedicalAttentionNighttime()));
							
							maxTable.setWidget(5, 0, new HTML("Level 2: " + results.getRequiresHospitalizationDaytime()));
							maxTable.setWidget(5, 1, new HTML("Level 2: " + results.getRequiresHospitalizationNighttime()));
							
							maxTable.setWidget(6, 0, new HTML("Level 3: " + results.getLifeThreateningInjuriesDaytime()));
							maxTable.setWidget(6, 1, new HTML("Level 3: " + results.getLifeThreateningInjuriesNighttime()));
							
							maxTable.setWidget(7, 0, new HTML("Level 4: " + results.getDeathsDaytime()));
							maxTable.setWidget(7, 1, new HTML("Level 4: " + results.getDeathsNighttime()));

							maxVp.add(maxTable);
							
							//content.setMaxContent(maxVp);
						}
						
					});
				}
			});
			info.addInfoWindowRestoreEndHandler(new InfoWindowRestoreEndHandler() {
				
				@Override
				public void onRestoreEnd(InfoWindowRestoreEndEvent event) {
					GWT.log("Restoring info window from max", null);
				}
			});
			
			info.open(marker, content);
			
		}
    	
    }

	public void onResize(ResizeEvent event) {
		onWindowResized(event.getWidth(), event.getHeight());
	}
}
